March 13, 2021
자바스크립트와 외부 API를 이용해 구현 하는 나만의 유튜브 강의실
영상 카드의 이모지 버튼을 클릭하여 아래와 같은 상태 변경이 가능해야 한다.
snackbar
를 통해 보여준다.키보드를 활용해 다양한 조작이 가능하도록 해야한다.
API key가 외부에 노출되지 않아야 한다.
지난 1단계 미션을 진행하면서 클라이언트에서 코드를 완전히 숨기는게 불가능 하다는 것을 꺠달았습니다.
같은 미션을 진행하며 비슷한 고민을 한 크루들이 많이 있었습니다. 크루들이 진행한 내용을 글로 정리해서 올려준 덕분에 간단하게 Netlify를 통한 API Request 우회 서버를 구성할 수 있었습니다.
Netlify를 통해 사용한 코드는 다음 레포지토리에서 보실 수 있습니다!
이전 미션인 행운의 로또 미션에서는 handleAccessibility
라는 함수를 만들어서 키보드를 통한 입력을 관리했습니다. 그런데 위 방법에는 두가지 문제가 있다고 생각했습니다.
window
에 등록하고 위임한다.handleAccessibility
라는 이름이 가르키는 범위가 너무 광범위하다.그래서 이번에는 이름을 조금 바꾸고 Object Literal을 통한 분기를 주는 방식을 적용했습니다.
import $DOM from '../../utils/DOM.js'
import { closeModal } from '../../view/modal.js'
import { onModalShow } from '../modal/visibility/onModalShow.js'
import { onToggleRenderedClips } from '../main/onToggleRenderedClips.js'
const actions = {
F1: () => onToggleRenderedClips({ target: $DOM.NAVIGATOR.UNWATCHED_BUTTON }),
F2: () => onToggleRenderedClips({ target: $DOM.NAVIGATOR.WATCHED_BUTTON }),
F3: () =>
$DOM.SEARCH_MODAL.CONTAINER.classList.contains('open')
? closeModal()
: onModalShow(),
Escape: () =>
$DOM.SEARCH_MODAL.CONTAINER.classList.contains('open') && closeModal(),
}
export const onWindowInput = ({ key }) => {
actions[key]?.()
}
1-2
에서 첨부한 함수를 보면 다음과 같은 구조로 되어있는걸 확인할 수 있습니다.
const actions = () => {
...
}
export const onWindowInput = ({ key }) => {
actions[key]?.()
}
위 코드를 작성하기 전 다른 크루의 미션 리뷰에 달린 다음 피드백을 발견할 수 있었습니다.
상수가 함수 안에 있을 필요가 있을까요? 함수가 호출될 때마다 메모리에 할당되는게 비효율적인거 같아서요!
이 피드백을 보고 최대한 export 하는 모듈들을 가볍게 만들면 어떨까라는 고민을 하면서 코드를 작성해봤습니다. 그러나 한편으로는 굳이 재사용되지 않는 actions
를 밖에 둬야할까? 라는 생각도 하고 있습니다. 아직 import, export되는 모듈들에 포함된 변수들이 어떻게 할당, 재할당 되는지에 대한 레퍼런스를 찾지는 못 했기 때문에 머리가 조금 더 복잡해지는거 같습니다.
혹시 이 글을 보시는 분들 중 이와 관련된 레퍼런스나 아티클을 알고 계시다면 알려주시면 감사하겠습니다..😢
step1을 진행할 때도 피드백 받았던 부분입니다.
매번 HTML tag들의 default type을 간과하고 코드를 작성하고 있는데 다시 한번 꼼꼼하게 확인해보는 습관을 들여야겠습니다.
snackbar를 보여주는 동작을 하는 util함수인데 ‘동작’이 빠져 어색함이 있었습니다.
함수의 네이밍에 좀 더 신경써야겠다는 생각을 했습니다.
setTimeout
을 통해 snackbar를 보여주고 있는데 기존 실행되고 있는 timer때문에 연속으로 snackbar를 보여줘야 되는 상황에서는 어색한 지연이 생기는 문제가 있었습니다.
기존 함수는 다음과 같이 작성되어 있었습니다.
import $DOM from './DOM.js'
export const snackbar = message => {
$DOM.SNACK_BAR.CONTINAER.innerText = message
$DOM.SNACK_BAR.CONTINAER.classList.toggle('show')
setTimeout(() => {
$DOM.SNACK_BAR.CONTINAER.classList.toggle('show')
}, 3000)
}
위 함수에 연속으로 snackbar가 호출될 경우 기존의 timer를 초기화 해주는 부분을 추가했습니다. 또한 기존처럼 innerText를 통해 변경했을 메세지가 바뀌고 빠르게 사라지는 오류를 발견하여 해당 부분도 수정하여 문제를 해결했습니다.
import $DOM from './DOM.js'
import { $ } from './querySelector.js'
let id
export const showSnackbar = message => {
$DOM.SNACK_BAR.CONTAINER.innerHTML = snackbar(message)
const $snackbarContainerMessage = $('[data-js="snackbar-container__message"]')
$snackbarContainerMessage.classList.toggle('show')
if (id) {
clearTimeout(id)
}
id = setTimeout(() => {
$snackbarContainerMessage.classList.toggle('show')
}, 3000)
}
그동안 Object Literal을 활용하여 코드를 작성할 때 다음과 key 값아 존재했을 때만 함수를 실행가능하도록 &&
논리 연산자를 통한 방어코드를 작성했습니다.
const actions = {
...
}
export const onButtonContainer = ({ target }) => {
actions[target.dataset.js] && actions[target.dataset.js](target);
};
이번 피드백을 통해 Optional Chaining으로도 위와 같은 방어코드를 작성 가능하다는걸 알게 됐습니다.
const actions = {
...
}
export const onButtonContainer = ({ target }) => {
actions[target.dataset.js]?.(target);
};
사실 피드백을 받기 전 이전의 코드를 개선할 생각조차 못 해 조금 부끄럽기도 합니다. 관습적으로 사용하던 코드들을 살펴보고 개선할 방법이 있는지 찾아봐야겠습니다.
위와 같은 피드백을 받고 찾아보니 다음과 같은 지원율을 보이고 있었습니다.
88%가 높은 지원율이 아닌만큼 크로스 브라우징을 고려할 경우 babel같은 트랜스파일러, 혹은 polyfil과 함께 사용해야 한다는 점을 알게 됐습니다.
핸들러를 정의하는 부분과 실제로 등록되는 부분의 거리가 너무 멀어서 실제로 핸들러가 어떻게 동작하는지 파악하기 힘들다.
기존 코드를 보면 아래와 같이 모듈들을 import 해서 사용한 경우가 있었습니다.
import { onModalClose } from './handler/modal/visibility/onModalClose.js'
디렉토리 구조를 나누면서 실제 해당 모듈을 사용하는 부분과 정의하는 부분이 너무 멀어진 것이 문제였습니다. 핸들러의 하위 디렉토리마다 export를 다시 정의하는 모듈을 추가해 다음과 같이 코드를 변경했습니다.
import { onWindowInput } from './onWindowInput.js'
import { onModalClose } from '../modal/visibility/onModalClose.js'
export default function() {
window.addEventListener('keyup', onWindowInput)
window.addEventListener('click', onModalClose)
}
import initMainHandler from './main/index.js'
import initModalHandler from './modal/index.js'
import initWindowHandler from './window/index.js'
export default function() {
initMainHandler()
initModalHandler()
initWindowHandler()
}
import initHandler from './handler/index.js'
export const YoutubeClassRoom = () => {
clearDeletedClip()
initDisplay()
initHandler()
}
window.onload = () => {
YoutubeClassRoom()
}
위와 같이 export하는 모듈들을 한번씩 묶어주면서 모듈 간의 거리를 가깝게 하면서 가독성이 올라갔다고 느꼈습니다.